using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace NNFramework.NeuroLibrary
{
    [Serializable()]
    public class clsLayer<TInput, TOutput> : clsBase
        where TInput : struct
        where TOutput : struct
    {
        #region Members Section

        private List<clsNeuron<TInput, TOutput>> mnlsNeurons;
        private LayerType mlytType;
        private int mintInputs;
        private int mintOutputs;
        private int mintIndex;
        private LayerArchitecture mlyaArchitect;

        #endregion

        #region Properties Section

        public LayerArchitecture Architecture
        {
            get
            {
                return mlyaArchitect;
            }
            set
            {
                mlyaArchitect = value;
            }
        }

        public int Inputs
        {
            get
            {
                return mintInputs;
            }
        }

        public int Outputs
        {
            get
            {
                return mintOutputs;
            }
        }

        public int Index
        {
            get
            {
                return mintIndex;
            }
        }

        public List<clsNeuron<TInput, TOutput>> Neurons
        {
            get
            {
                return mnlsNeurons;
            }
        }
        

        #endregion

        #region Constructors Section

        public clsLayer()
        {
            mnlsNeurons = new List<clsNeuron<TInput, TOutput>>();
            mlytType = LayerType.HiddenLayer;
            mintIndex = 0;
            mlyaArchitect = LayerArchitecture.Other;
        }

        public clsLayer(LayerType plytType)
        {
            mnlsNeurons =  new List<clsNeuron<TInput,TOutput>>();
            mlytType = plytType;
            mintIndex = 0;
            mlyaArchitect = LayerArchitecture.Other;
        }

        public clsLayer(LayerType plytType, int pintIndex)
        {
            mnlsNeurons = new List<clsNeuron<TInput, TOutput>>();
            mlytType = plytType;
            mintIndex = pintIndex;
            mlyaArchitect = LayerArchitecture.Other;
        }

        public clsLayer(LayerType plytType, int pintIndex, LayerArchitecture plyaArchitecture)
        {
            mnlsNeurons = new List<clsNeuron<TInput, TOutput>>();
            mlytType = plytType;
            mintIndex = pintIndex;
            mlyaArchitect = plyaArchitecture;
        }

        #endregion

        #region Private Methods Section

        private void Refresh()
        {
            mintInputs = 0;
            mintOutputs = 0;
            for (int i = 0; i < mnlsNeurons.Count; i++)
            {
                mintOutputs++;
                mintInputs += mnlsNeurons[i].DendritesCount;
            }
        }

        #endregion

        #region Public Methods Section

        public void AddNeuron(clsNeuron<TInput, TOutput> pcnnNeuron)
        {
            this.mnlsNeurons.Add(pcnnNeuron);
            this.Refresh();
        }        

        public void LoadNewNeuronFromXml(string pstrFileName)
        {            
            this.AddNeuron(clsNeuron<TInput, TOutput>.LoadNeuronFromXml(pstrFileName));
        }

        public void LoadNewNeuronFromBin(string pstrFileName)
        {
            this.AddNeuron(clsNeuron<TInput, TOutput>.LoadNeuronFromXml(pstrFileName));
        }

        public void FullConnectToLayer(object pclyLayer)
        {
            if (pclyLayer.GetType().IsGenericType)
            {
                Type[] typToLayer = pclyLayer.GetType().GetGenericArguments();
                if ((typToLayer.Length < 3) && (typToLayer.Length > 0))
                {
                    Type[] typFromLayer = this.GetType().GetGenericArguments();
                    if (typFromLayer[1] == typToLayer[0])
                    {                        
                        int intNeurons = (int)pclyLayer.GetType().GetProperty("Outputs").GetValue(pclyLayer, null);
                        //the method don't accept another thing that null or an array 
                        object objToNeurons = pclyLayer.GetType().GetProperty("Neurons").GetValue(pclyLayer, null);
                        object objCurrNeuron;
                        object[] objParams =  new object[3];
                        for (int i = 0; i < intNeurons; i++)
                        {
                            objCurrNeuron = objToNeurons.GetType().GetProperty("Item").GetValue(objToNeurons, new object[1] { i });                      
                            foreach (clsNeuron<TInput, TOutput> objFromNeuron in this.Neurons)
                            {
                                objParams[0] = objFromNeuron;
                                objParams[1] = new Random().NextDouble();
                                objParams[2] = objFromNeuron.Axon;
                                objCurrNeuron.GetType().GetMethod("AddDendriteReflected").Invoke(objCurrNeuron, BindingFlags.ExactBinding, null, objParams, null);                                 
                            }
                        }
                    }
                    else
                    {
                        throw new ArgumentException("The Output Type from the Origin Layer is different of the Input Type of the Destination Layer");
                    }
                }
                else
                {
                    throw new ArgumentException("The object isn't instanciated from correct a Generic Type");
                }
                
            }
            else
            {
                
                throw new ArgumentException("The object isn't instanciated from a Generic Type");
            }
        }



        

        #endregion

        #region Public Static Methods Section

        public static clsLayer<TInput, TOutput> LoadLayerFromXml(string pstrFileName)
        {
            XmlSerializer xslSerializer = new XmlSerializer(typeof(clsLayer<TInput, TOutput>));
            FileStream flsFile = new FileStream(pstrFileName, FileMode.Open);
            return (clsLayer<TInput, TOutput>)xslSerializer.Deserialize(flsFile);
        }

        public static clsLayer<TInput, TOutput> LoadLayerFromBin(string pstrFileName)
        {
            IFormatter iftBinary = new BinaryFormatter();
            Stream stmFile = new FileStream(pstrFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            return (clsLayer<TInput, TOutput>)iftBinary.Deserialize(stmFile);
        }

        #endregion


    }
}
